package util;

import org.apache.logging.log4j.LogManager;

import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * ClassName: BaseDao
 * Description: 数据库操作的基础类，提供基本的查询、更新方法，并支持嵌套对象的处理。
 * @Author L_D_S
 * @Create 2024/5/13 1:28
 * @Version 2.5
 */
public class BaseDao {

    private static final Logger logger = Logger.getLogger(BaseDao.class.getName());

    /**
     * 使用JDBC查询数据库并映射结果集到指定类型对象
     *
     * @param <T> 泛型参数，指定要映射的类型
     * @param clazz 要映射的类类型
     * @param sql SQL查询语句
     * @param args 查询参数
     * @return 返回查询结果映射的对象，如果没有结果则返回null
     */
    public <T> T baseQueryObject(Class<T> clazz, String sql, Object... args) {
        T result = null;
        Connection connection = DbUtil.getConnection();
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            setParameters(preparedStatement, args);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    result = mapRowToObject(clazz, resultSet);
                }
            }
        } catch (Exception e) {
            logger.severe("查询失败：" + e.getMessage());
        } finally {
            DbUtil.releaseConnection(connection);
        }
        return result;
    }


    public <T> T baseQueryObject(Connection connection,Connection expectedConnection,Class<T> clazz, String sql, Object... args) throws Exception {
        // 检验连接是否一致
        if (connection == null || connection != expectedConnection) {
            throw new IllegalArgumentException("数据库连接不一致或连接为null");
        }

        // 检查是否在事务中(即自动提交模式是否被禁用)
        if (connection.getAutoCommit()) {
            throw new IllegalStateException("当前数据库连接未处于事务中，请先关闭自动提交模式");
        }

        T result = null;
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            setParameters(preparedStatement, args);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    result = mapRowToObject(clazz, resultSet);
                }
            }
        } catch (Exception e) {
            // 异常处理：打印堆栈跟踪
            logger.severe("查询失败：" + e.getMessage());
        }
        return result;
    }


    /**
     * 基础查询方法，用于根据提供的SQL语句和参数，查询数据库并返回结果列表
     *
     * @param <T> 泛型参数，表示要查询的对象类型
     * @param clazz 对象类的Class对象，用于实例化和反射操作
     * @param sql 查询SQL语句
     * @param args 查询参数
     * @return 包含查询结果的列表，如果没有结果则返回空列表
     */
    public <T> List<T> baseQuery(Class<T> clazz, String sql, Object... args) {
        // 存储查询结果的列表
        List<T> list = new ArrayList<>();
        // 获取数据库连接
        Connection connection = DbUtil.getConnection();
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            // 设置预编译语句的参数
            setParameters(preparedStatement, args);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                // 获取结果集的元数据，用于处理列信息
                ResultSetMetaData metaData = resultSet.getMetaData();
                // 获取列数
                int columnCount = metaData.getColumnCount();

                // 遍历结果集的每一行
                while (resultSet.next()) {
                    // 通过反射创建对象实例
                    T obj = clazz.getDeclaredConstructor().newInstance();
                    // 将结果集的一行数据映射到对象
                    mapRowToObject(clazz, obj, resultSet);
                    // 将对象添加到结果列表
                    list.add(obj);
                }
            }
        } catch (Exception e) {
            // 异常处理：打印堆栈跟踪
            logger.severe("查询失败：" + e.getMessage());
        } finally {
            // 释放数据库连接
            DbUtil.releaseConnection(connection);
        }
        // 返回查询结果列表
        return list;
    }


    /**
     *  执行查询操作，并返回查询结果列表，运用事务。
     * @param connection    数据库连接对象，用于获取PreparedStatement对象
     * @param expectedConnection   期望的数据库连接对象，用于校验连接一致性
     * @param clazz 要映射的对象类型
     * @param sql   SQL更新语句（可以是SELECT)
     * @param args  SQL语句的参数，可变参数列表，根据sql语句中参数占位符的个数和类型传入相应的值
     * @return  返回查询到的全部数据
     * @param <T>
     * @throws Exception
     */
    public <T> List<T> baseQuery(Connection connection,Connection expectedConnection, Class<T> clazz, String sql, Object... args) throws Exception {
        // 检验连接是否一致
        if (connection == null || connection != expectedConnection) {
            throw new IllegalArgumentException("数据库连接不一致或连接为null");
        }

        // 检查是否在事务中(即自动提交模式是否被禁用)
        if (connection.getAutoCommit()) {
            throw new IllegalStateException("当前数据库连接未处于事务中，请先关闭自动提交模式");
        }

        // 存储查询结果的列表
        List<T> list = new ArrayList<>();
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            // 设置预编译语句的参数
            setParameters(preparedStatement, args);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                // 获取结果集的元数据，用于处理列信息
                ResultSetMetaData metaData = resultSet.getMetaData();
                // 获取列数
                int columnCount = metaData.getColumnCount();

                // 遍历结果集的每一行
                while (resultSet.next()) {
                    // 通过反射创建对象实例
                    T obj = clazz.getDeclaredConstructor().newInstance();
                    // 将结果集的一行数据映射到对象
                    mapRowToObject(clazz, obj, resultSet);
                    // 将对象添加到结果列表
                    list.add(obj);
                }
            }
        } catch (Exception e) {
            // 异常处理：打印堆栈跟踪
            logger.severe("查询失败：" + e.getMessage());
        }
        // 返回查询结果列表
        return list;
    }


    /**
     * 执行SQL更新操作的基础方法
     * 该方法主要用于执行INSERT、UPDATE、DELETE等操作，返回受影响的行数
     *
     * @param sql 要执行的SQL语句，可以带参数占位符
     * @param args SQL语句中参数占位符的实际参数，变长参数列表
     * @return 受影响的行数
     */
    // 通用的增删改方法，开启事务处理
    public int baseUpdate(String sql, Object... args) {
        int rows = 0;
        Connection connection = DbUtil.getConnection();
        try {

            try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                setParameters(preparedStatement, args);
                rows = preparedStatement.executeUpdate();
            }
        } catch (SQLException e) {
            logger.severe("更新失败：" + e.getMessage());
        } finally {
            DbUtil.releaseConnection(connection);
        }
        return rows;
    }

    /**
     * 执行SQL更新操作的基础方法
     * 该方法通过接收数据库连接、SQL更新语句（如INSERT、UPDATE、DELETE）及相应的参数，来执行SQL语句，并返回受影响的行数
     * 使用PreparedStatement来防SQL注入，并且通过try-with-resources语句确保资源的正确关闭，避免资源泄露
     *
     * @param connection 数据库连接对象，用于获取PreparedStatement对象
     * @param expectedConnection 期望的数据库连接对象，用于校验连接一致性
     * @param sql SQL更新语句（可以是INSERT、UPDATE、DELETE等），使用？作为参数占位符
     * @param args SQL语句的参数，可变参数列表，根据sql语句中参数占位符的个数和类型传入相应的值
     * @return 受影响的行数，代表SQL更新操作影响的数据行数
     * @throws SQLException 如果发生SQL错误，抛出异常而不是捕获
     */
    public int baseUpdate(Connection connection, Connection expectedConnection, String sql, Object... args) throws SQLException {
        // 校验连接是否一致
        if (connection == null || connection != expectedConnection) {
            logger.severe("数据库连接不一致或连接为null");
        }

        // 检查是否在事务中（即自动提交模式是否被禁用）
        if (connection.getAutoCommit()) {
            logger.severe("当前数据库连接未处于事务中，请先关闭自动提交模式");
        }

        // 初始化受影响的行数为0
        int rows = 0;
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            // 设置SQL语句的参数
            setParameters(preparedStatement, args);
            // 执行SQL更新操作，并将受影响的行数赋值给rows变量
            rows = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            // 捕获并重新抛出SQL异常，便于外部捕获处理
            throw new SQLException("SQL更新操作失败", e);
        }
        // 返回受影响的行数
        return rows;
    }


    /**
     * 根据SQL查询语句获取指定类的指定字段的值
     *
     * 该方法通过传入的类类型、SQL查询语句、字段名以及参数，从数据库中查询并返回该字段的值
     * 主要用于根据某个条件获取特定对象的特定属性值，适用于简单的查询场景
     *
     * @param <T> 泛型标记，表示要查询的对象的类型
     * @param clazz 对象的类类型，用于将结果集转换为指定对象
     * @param sql SQL查询语句，用于从数据库中获取数据
     * @param fieldName 字段名，表示要获取的属性名
     * @param args 查询参数，用于SQL查询语句中的占位符
     * @return 返回查询到的字段值如果查询结果为空或出现异常，则返回null
     */
    public <T> Object baseQueryObjectAttribute(Class<T> clazz, String sql, String fieldName, Object... args) {
        Object result = null;
        Connection connection = DbUtil.getConnection();
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            setParameters(preparedStatement, args);

            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    result = getAttributeValueFromResultSet(clazz, fieldName, resultSet);
                }
            }
        } catch (Exception e) {
            logger.severe("查询失败：" + e.getMessage());
        } finally {
            DbUtil.releaseConnection(connection);
        }
        return result;
    }

    /**
     * 从ResultSet中提取指定字段的属性值
     *
     * @param <T>     泛型标记，表示返回类型
     * @param clazz   目标对象的类，用于类型检查和类型转换
     * @param fieldName   指定的字段名，用于在ResultSet中查找对应的列
     * @param resultSet   数据库查询结果集，从其中提取字段值
     * @return  返回提取的字段值，如果值是LocalDateTime类型，则转换为Timestamp类型
     * @throws Exception   如果字段未在ResultSet中找到，则抛出NoSuchFieldException
     */
    private <T> Object getAttributeValueFromResultSet(Class<T> clazz, String fieldName, ResultSet resultSet) throws Exception {
        // 获取ResultSet的元数据，用于访问列信息
        ResultSetMetaData metaData = resultSet.getMetaData();
        // 获取ResultSet中的列数
        int columnCount = metaData.getColumnCount();

        // 遍历ResultSet的所有列，寻找匹配的字段名
        for (int i = 1; i <= columnCount; i++) {
            // 获取当前列的标签名，即列在ResultSet中的别名
            String columnName = metaData.getColumnLabel(i);
            // 检查当前列的标签名是否与目标字段名匹配，忽略大小写
            if (columnName.equalsIgnoreCase(fieldName)) {
                // 如果匹配，则获取该列的值
                Object value = resultSet.getObject(columnName);
                // 检查值是否为LocalDateTime类型，如果是，则转换为SQL兼容的Timestamp类型
                if (value instanceof LocalDateTime) {
                    value = Timestamp.valueOf((LocalDateTime) value);
                }
                // 返回提取的字段值
                return value;
            }
        }
        // 如果遍历完所有列后仍未找到匹配的字段名，则抛出异常
        throw new NoSuchFieldException("Field not found in ResultSet: " + fieldName);
    }


    /**
     * 为PreparedStatement对象设置参数
     *
     * @param preparedStatement 预编译的SQL语句对象
     * @param args 参数对象数组，用于传递SQL语句中需要设置的各种参数
     * @throws SQLException 如果设置参数过程中产生SQL异常
     *
     * 本方法通过循环遍历参数对象数组args，将每个参数对象设置到预编译SQL语句对象（preparedStatement）中对应的位置
     * 参数索引从1开始，因此在设置参数时，索引值需要加1
     */
    private void setParameters(PreparedStatement preparedStatement, Object... args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
    }


    /**
     * 将数据库查询结果集（ResultSet）中的一行数据转换为指定类型（clazz）的对象实例
     * 此方法支持基本数据类型及其包装类，字符串，以及自定义类对象的转换
     * 对于自定义类，如果其实例无法通过默认构造函数创建，则转换不会进行
     *
     * @param clazz 要转换的目标类型，可以是基本数据类型、String、LocalDateTime或自定义类
     * @param resultSet 数据库查询结果集，包含待转换的数据
     * @return 转换后的对象实例，如果转换失败或不支持则返回null
     * @throws Exception 如果resultSet处理中出现错误则抛出异常
     */
    private <T> T mapRowToObject(Class<T> clazz, ResultSet resultSet) throws Exception {
        // 检查 clazz 是否为 Integer 类型
        if (clazz.equals(Integer.class)) {
            return clazz.cast(resultSet.getInt(1));
        }
        // 检查 clazz 是否为 Long 类型
        else if (clazz.equals(Long.class)) {
            return clazz.cast(resultSet.getLong(1));
        }
        // 检查 clazz 是否为 Boolean 类型
        else if (clazz.equals(Boolean.class)) {
            return clazz.cast(resultSet.getBoolean(1));
        }
        // 检查 clazz 是否为 Double 类型
        else if (clazz.equals(Double.class)) {
            return clazz.cast(resultSet.getDouble(1));
        }
        // 检查 clazz 是否为 Float 类型
        else if (clazz.equals(Float.class)) {
            return clazz.cast(resultSet.getFloat(1));
        }
        // 检查 clazz 是否为 Short 类型
        else if (clazz.equals(Short.class)) {
            return clazz.cast(resultSet.getShort(1));
        }
        // 检查 clazz 是否为 Byte 类型
        else if (clazz.equals(Byte.class)) {
            return clazz.cast(resultSet.getByte(1));
        }
        // 检查 clazz 是否为 Character 类型
        else if (clazz.equals(Character.class)) {
            String str = resultSet.getString(1);
            if (str != null && !str.isEmpty()) {
                return clazz.cast(str.charAt(0));
            }
        }
        // 检查 clazz 是否为 String 类型
        else if (clazz.equals(String.class)) {
            return clazz.cast(resultSet.getString(1));
        }
        // 检查 clazz 是否为 LocalDateTime 类型
        else if (clazz.equals(LocalDateTime.class)) {
            return clazz.cast(resultSet.getTimestamp(1).toLocalDateTime());
        } else if (clazz.equals(LocalDate.class)) {
            return clazz.cast(resultSet.getDate(1).toLocalDate());
        }
        // 对于其他类，尝试使用默认构造函数创建实例并调用 mapRowToObject 方法填充属性
        else {
            T obj = clazz.getDeclaredConstructor().newInstance();
            mapRowToObject(clazz, obj, resultSet);
            return obj;
        }
        // 如果不支持转换，则返回 null
        return null;
    }

    /**
     * 将数据库查询结果集映射到对象的字段
     *
     * @param clazz 目标对象的类类型，用于反射操作
     * @param obj 目标对象，将数据映射到此对象的字段
     * @param resultSet 数据库查询结果集
     * @throws Exception 如果反射操作或数据赋值出现问题
     */
    private <T> void mapRowToObject(Class<T> clazz, T obj, ResultSet resultSet) throws Exception {
        // 获取结果集的元数据，用于获取列信息
        ResultSetMetaData metaData = resultSet.getMetaData();
        // 获取列的数量
        int columnCount = metaData.getColumnCount();
        // 遍历每一列
        for (int i = 1; i <= columnCount; i++) {
            // 获取列名
            String columnName = metaData.getColumnLabel(i);
            // 获取列值
            Object value = resultSet.getObject(columnName);
            // 如果值是LocalDateTime类型，将其转换为Timestamp类型，以兼容数据库类型
            if (value instanceof LocalDateTime) {
                value = Timestamp.valueOf((LocalDateTime) value);
            }
            // 将列值设置到对象的对应字段中
            setFieldValue(clazz, obj, columnName, value, resultSet);
        }
    }


    /**
     * 设置对象的字段值
     *
     * @param clazz 要操作的类类型
     * @param obj 要操作的对象实例
     * @param fieldName 字段名
     * @param value 字段值
     * @param resultSet 结果集，用于填充嵌套对象的字段
     * @throws Exception 如果发生错误，抛出异常
     */
    private <T> void setFieldValue(Class<T> clazz, T obj, String fieldName, Object value, ResultSet resultSet) throws Exception {
        try {
            // 获取指定名称的字段，忽略访问控制修饰符
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            // 获取字段的数据类型
            Class<?> fieldType = field.getType();
            // 如果值为null，直接设置
            if (value == null) {
                field.set(obj, null);
                return;
            }
            // 如果字段类型和获取到的值类型匹配，直接设置
            if (fieldType.isAssignableFrom(value.getClass())) {
                field.set(obj, value);
            } else if (isPrimitiveOrWrapper(fieldType) || fieldType == String.class) {
                // 如果是基本类型或者字符串，进行类型转换并设置
                field.set(obj, convertPrimitiveOrString(fieldType, value));
            } else {
                // 处理嵌套对象
                Object nestedObject = fieldType.getDeclaredConstructor().newInstance();
                mapNestedObjectFields(fieldType, nestedObject, resultSet);
                field.set(obj, nestedObject);
            }
        } catch (NoSuchFieldException e) {
            // 如果字段不存在，抛出异常
        }
    }


    /**
     * 根据字段类型转换给定值
     *
     * 该方法旨在将值转换为指定字段类型的适当形式它支持各种原始类型（如int、boolean、double等）
     * 以及它们对应的包装类如果字段类型不是原始类型或其包装类，则返回值的字符串表示形式
     *
     * @param fieldType 字段的类型，可以是原始类型或其对应的包装类
     * @param value 需要转换的值，将根据fieldType被转换
     * @return 根据字段类型转换后的值如果字段类型不是原始类型或其包装类，
     *         则返回值的字符串表示形式
     */
    private Object convertPrimitiveOrString(Class<?> fieldType, Object value) {
        // 当字段类型为boolean或Boolean时，将值转换为Boolean对象
        if (fieldType == boolean.class || fieldType == Boolean.class) {
            return Boolean.valueOf(value.toString());
        } else if (fieldType == byte.class || fieldType == Byte.class) {
            // 当字段类型为byte或Byte时，将值转换为Byte对象
            return Byte.valueOf(value.toString());
        } else if (fieldType == char.class || fieldType == Character.class) {
            // 当字段类型为char或Character时，将值的首个字符作为结果
            return value.toString().charAt(0);
        } else if (fieldType == short.class || fieldType == Short.class) {
            // 当字段类型为short或Short时，将值转换为Short对象
            return Short.valueOf(value.toString());
        } else if (fieldType == int.class || fieldType == Integer.class) {
            // 当字段类型为int或Integer时，将值转换为Integer对象
            return Integer.valueOf(value.toString());
        } else if (fieldType == long.class || fieldType == Long.class) {
            // 当字段类型为long或Long时，将值转换为Long对象
            return Long.valueOf(value.toString());
        } else if (fieldType == float.class || fieldType == Float.class) {
            // 当字段类型为float或Float时，将值转换为Float对象
            return Float.valueOf(value.toString());
        } else if (fieldType == double.class || fieldType == Double.class) {
            // 当字段类型为double或Double时，将值转换为Double对象
            return Double.valueOf(value.toString());
        } else {
            // 对于其他类型，返回值的字符串表示形式
            return value.toString();
        }
    }


    /**
     * 判断给定的类类型是否为基本类型或其包装类
     *
     * @param fieldType 要判断的类类型
     * @return 如果是基本类型或其包装类，则返回true；否则返回false
     */
    private boolean isPrimitiveOrWrapper(Class<?> fieldType) {
        // 判断给定的类类型是否为Java的基本类型 或者 判断是否为Java的基本类型的包装类
        return fieldType.isPrimitive() ||
                fieldType == Boolean.class || fieldType == Byte.class || fieldType == Character.class ||
                fieldType == Short.class || fieldType == Integer.class || fieldType == Long.class ||
                fieldType == Float.class || fieldType == Double.class;
    }


    /**
     * 将数据库查询结果映射到嵌套对象的字段中
     * 此方法用于处理嵌套对象，通过反射机制将数据库查询结果集中的数据映射到对象的字段上
     *
     * @param nestedClass 嵌套对象的类类型，用于反射获取字段信息
     * @param nestedObject 嵌套对象，其字段将被映射数据库查询结果
     * @param resultSet 数据库查询结果集，包含要映射的数据
     * @throws Exception 在字段访问被拒绝或类型不匹配时抛出异常
     */
    private void mapNestedObjectFields(Class<?> nestedClass, Object nestedObject, ResultSet resultSet) throws Exception {
        // 遍历嵌套对象的所有声明的字段，包括私有字段
        for (Field nestedField : nestedClass.getDeclaredFields()) {
            // 设置字段可访问，以绕过访问修饰符
            nestedField.setAccessible(true);
            // 获取字段名称，用于从结果集中提取数据
            String nestedFieldName = nestedField.getName();

            try {
                // 从结果集中获取与字段名称对应的值
                Object nestedValue = resultSet.getObject(nestedFieldName);

                // 如果结果集中存在该字段的值，则进行处理
                if (nestedValue != null) {
                    // 如果字段类型与结果集中值的类型匹配，则直接设置字段的值
                    if (nestedField.getType().isAssignableFrom(nestedValue.getClass())) {
                        nestedField.set(nestedObject, nestedValue);
                    }
                    // 对于基本类型或其包装类类型，或者字符串类型字段，进行类型转换后设置值
                    else if (isPrimitiveOrWrapper(nestedField.getType()) || nestedField.getType() == String.class) {
                        nestedField.set(nestedObject, convertPrimitiveOrString(nestedField.getType(), nestedValue));
                    }
                } else {
                    // 如果结果集中没有该字段的值，则设置为 null
                    nestedField.set(nestedObject, null);
                }
            } catch (SQLException e) {
                // 如果找不到对应的列名，则设置为 null
                nestedField.set(nestedObject, null);
            }
        }
    }
}
